/*
 * Copyright 2024 The CHANGLEI Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import 'dart:io';

import 'package:fibber/src/assets/yaml.dart';
import 'package:fibber/src/logger.dart';
import 'package:fibber/src/name.dart';
import 'package:fibber/src/spec.dart';
import 'package:fibber/src/writer.dart';
import 'package:path/path.dart' as path;

final _classnameGenerator = NameGenerator();

Future<void> resolve(String root, String output, [bool append = true, String? package, List<String>? ignores]) async {
  final pubspecFile = File(path.join(r'pubspec.yaml'));
  if (!pubspecFile.existsSync()) {
    Level.error.writeln(r'未发现`pubspec.yaml`，请在项目根目录执行');
    return;
  }
  final directory = Directory(root);
  if (!directory.existsSync()) {
    Level.error.writeln('文件夹`${directory.path}`不存在');
    return;
  }
  if (ignores != null && ignores.isNotEmpty) {
    Level.error.writeln('符合$ignores条件的文件和文件夹将会忽略');
  }
  final ignoredDirectories = <String>[r'忽略目录：'];
  final ignoredFiles = <String>[r'忽略文件：'];
  final directories = <String>[];
  final specs = <Spec>[
    ExtensionType(
      name: Name.underline,
      path: r'Assets.Name',
      constructor: Name.empty,
      methods: [
        Getter(
          name: r'name'.named,
          returns: Name.string,
          doc: r'See [basename]',
          constructor: r'basename(this)',
        ),
      ],
    ),
  ];
  final subSpecs = List.of(
    _toSpecs(
      relative: path.posix.joinAll(List.of(path.split(root))..removeLast()),
      directory: directory,
      append: append,
      package: package,
      ignores: ignores,
      ignoredDirectories: ignoredDirectories,
      ignoredFiles: ignoredFiles,
      directories: directories,
    ),
  );
  specs.addAll(subSpecs..sort());
  if (ignoredDirectories.length > 1) {
    Level.normal.writeln(ignoredDirectories.join('\n$indent'));
  }
  if (ignoredFiles.length > 1) {
    Level.normal.writeln(ignoredFiles.join('\n$indent'));
  }
  final name = directory.nameWithoutExtension;
  writeSpecsFile(
    output,
    name,
    specs,
    imports: [
      const Import(
        r'package:path/path.dart',
        show: [
          r'basename',
        ],
      ),
    ],
  );

  await writePubspecYaml(pubspecFile, directories);
}

bool _isIgnored(List<String>? ignores, FileSystemEntity entity) {
  if (ignores == null || ignores.isEmpty) {
    return false;
  }
  final entityPath = entity.path;
  final entityName = entity.name;
  return ignores.any((element) {
    final regExp = RegExp(RegExp.escape(element));
    return regExp.hasMatch(entityPath) || regExp.hasMatch(entityName) || path.isWithin(element, entityPath);
  });
}

String _ignoredReason(String path, String reason) {
  return '$path ${Level.warn('($reason)')}';
}

Iterable<NameSpec> _toSpecs({
  required String relative,
  required Directory directory,
  required bool append,
  required String? package,
  required List<String>? ignores,
  required List<String> ignoredDirectories,
  required List<String> ignoredFiles,
  required List<String> directories,
}) sync* {
  final directoryPath = path.posix.joinAll(path.split(directory.path));
  final directoryName = directory.name;
  if (!directory.existsSync()) {
    ignoredDirectories.add(_ignoredReason(directoryPath, r'不存在'));
    return;
  }
  if (_isIgnored(ignores, directory)) {
    ignoredDirectories.add(_ignoredReason(directoryPath, r'在`--ignore`中'));
    return;
  }

  assert(path.isWithin(relative, directoryPath));
  final paths = append ? path.split(path.relative(directoryPath, from: relative)) : [directoryName];
  final classname = _classnameGenerator.classOfAll(paths.toList());
  if (classname.isEmpty) {
    ignoredDirectories.add(_ignoredReason(directoryPath, r'名称不合法'));
    return;
  }

  final entities = List.of(directory.listSync(followLinks: false))
    ..sort((a, b) {
      return a.nameWithoutExtension.compareTo(b.nameWithoutExtension);
    });
  if (entities.isEmpty) {
    ignoredDirectories.add(_ignoredReason(directoryPath, r'空文件夹'));
    return;
  }

  final fields = <Field>[];
  final fieldNameGenerator = NameGenerator();
  for (final entity in entities) {
    if (entity is Directory) {
      yield* _toSpecs(
        relative: relative,
        directory: entity,
        append: append,
        package: package,
        ignores: ignores,
        ignoredDirectories: ignoredDirectories,
        ignoredFiles: ignoredFiles,
        directories: directories,
      ).toList();
    } else {
      final filepath = path.posix.joinAll(path.split(entity.path));
      final filename = entity.nameWithoutExtension;
      if (!entity.existsSync()) {
        ignoredFiles.add(_ignoredReason(filepath, r'不存在'));
        continue;
      }
      if (_isIgnored(ignores, entity)) {
        ignoredFiles.add(_ignoredReason(filepath, r'在`--ignore`中'));
        continue;
      }
      final fieldName = fieldNameGenerator.fieldOf(filename);
      if (fieldName.isEmpty) {
        ignoredFiles.add(_ignoredReason(filepath, r'名称不合法'));
        continue;
      }
      fields.add(
        AssetsField(
          name: fieldName,
          path: filepath,
          classname: classname,
          package: package,
        ),
      );
    }
  }

  fields.sort();

  if (fields.isNotEmpty) {
    directories.add('$directoryPath${path.posix.separator}');
    yield ExtensionType(
      name: classname,
      path: directoryPath,
      fields: fields,
      superClass: Name.underline,
    );
  }
}
